# uncompyle6 version 3.2.3
# Python bytecode 3.6 (3379)
# Decompiled from: Python 3.6.8 |Anaconda custom (64-bit)| (default, Feb 21 2019, 18:30:04) [MSC v.1916 64 bit (AMD64)]
# Embedded file name: api\handlers\frontend\scans.py
__author__ = "sanyi"
import uuid, dateutil.rrule, psycopg2, datetime, logging
from dateutil.tz import tzutc
from sqlalchemy import desc as sql_desc
from sqlalchemy import not_ as sql_not
from sqlalchemy import select as sql_select
from sqlalchemy import func as sql_func
from sqlalchemy import or_ as sql_or
from sqlalchemy.sql.expression import nullslast as sql_nulls_last
from api.classes.frontend.auto import *
from api.filters import Filters
from api.filters.sqlalchemy_adapter import get_sqlachemy_filter
from api.handlers.frontend import FrontendResourceHandler
from db.data.access import (
    generate_scan_ext_query,
    generate_scan_query,
    apply_filters,
    generate_target_xh_query,
    generate_scan_session_query,
    generate_target2group_query,
)
from db.data.users import (
    require_at_least_tester,
    get_owner_id,
    require_at_least_tech_admin,
)
from db.tables.users import UserConfirmation
from db.tables.targets import (
    TargetRow,
    TargetsTable,
    TargetTypes,
    TargetVerificationTypes,
)
from db.tables.events import create_event, EventResources
from db.tables.profiles import ProfilesTable, ProfileRow
from db.tables.scan_session_jobs import ScanSessionJobRow, ScanSessionJobsTable
from db.tables.scans import ScanRow, ScansTable
from db.tables.target2group import Target2GroupRow
from db.views.scans_extended import ScanExtendedViewRow
from db.tables.scan_sessions import ScanSessionsTable, ScanSessionRow
from db import Connection
from server.http_responses.http_errors import BadApiRequest, Http409, Http404, ApiErrors
from server.http_responses.http_status_codes import *
from settings_provider import settings
from db.views.targets_excluded_hours import TargetsWithExcludedHoursViewRow
from helpers.excluded_hours import (
    get_next_date_allowed_to_scan,
    is_allowed_to_scan_now,
    get_system_excluded_hours,
)
from helpers.address import get_local_addresses
from helpers.setup_types import (
    is_setup_type_aws,
    is_setup_type_on_premise_master,
    has_feature,
    SetupFeatures,
)
from helpers.constants.jobs import ScanAppTypes
from helpers.constants.scans import ScanPauseReasonTypes
from dateutil.tz import tzlocal

scan_filters = {
    "keys": {
        "status": {
            "value_type": "string",
            "multiple_values": True,
            "allowed_values": (
                "scheduled",
                "queued",
                "starting",
                "processing",
                "completed",
                "aborting",
                "aborted",
                "failed",
                "paused",
                "pausing",
                "resuming",
            ),
        },
        "criticality": {
            "value_type": "int",
            "multiple_values": True,
            "operators": ("eq", "ne", "le", "ge", "lt", "gt"),
        },
        "threat": {
            "value_type": "int",
            "multiple_values": True,
            "operators": ("eq", "ne", "le", "ge", "lt", "gt"),
        },
        "profile_id": {
            "value_type": "uuid4",
            "multiple_values": True,
            "operators": ("eq", "ne"),
        },
        "scan_id": {
            "value_type": "uuid4",
            "multiple_values": True,
            "operators": ("eq", "ne"),
        },
        "target_id": {
            "value_type": "uuid4",
            "multiple_values": True,
            "operators": ("eq", "ne"),
        },
        "group_id": {"value_type": "uuid4", "multiple_values": True},
    }
}
_logger = logging.getLogger("frontend_api_resources.Scans")


class Scans(FrontendResourceHandler):
    def get_scans(self, l, q=None, c=None, **_):
        """
        Accessible by everyone with access to the targets
        :param l:
        :param q:
        :param c:
        :param _:
        :return:
        """
        limit = min(l, 100) if l is not None else 100
        c = int(c) if c is not None else 0
        cursor = c
        filters = Filters(scan_filters, q)
        joined_tables = []
        select_from, tail_filters = generate_scan_ext_query(self.request_user)
        select_from = select_from.join(
            ProfilesTable, ProfileRow.profile_id == ScanExtendedViewRow.profile_id
        )
        tail_filters.append(sql_not(ScanExtendedViewRow.continuous.is_(True)))
        joined_tables.append(ProfilesTable)
        for filter_key, filter_expression in filters.data.items():
            flt = None
            if filter_key == "criticality":
                if TargetsTable not in joined_tables:
                    select_from = select_from.join(
                        TargetsTable,
                        TargetRow.target_id == ScanExtendedViewRow.target_id,
                    )
                    joined_tables.append(TargetsTable)
                flt = get_sqlachemy_filter(
                    filter_expression,
                    TargetRow.criticality,
                    filters.descriptor["keys"][filter_key],
                )
            else:
                if filter_key == "status":
                    flt = get_sqlachemy_filter(
                        filter_expression,
                        ScanExtendedViewRow.current_status,
                        filters.descriptor["keys"][filter_key],
                    )
                else:
                    if filter_key == "threat":
                        flt = get_sqlachemy_filter(
                            filter_expression,
                            ScanExtendedViewRow.current_threat,
                            filters.descriptor["keys"][filter_key],
                        )
                    else:
                        if filter_key == "group_id":
                            flt = get_sqlachemy_filter(
                                filter_expression,
                                Target2GroupRow.group_id,
                                filters.descriptor["keys"][filter_key],
                            )
                            sf, tf = generate_target2group_query(self.request_user)
                            sq = sql_select((Target2GroupRow.target_id,)).select_from(
                                sf
                            )
                            sq = apply_filters(sq, tf)
                            flt = ScanExtendedViewRow.target_id.in_(sq.where(flt))
                        else:
                            if filter_key == "profile_id":
                                flt = get_sqlachemy_filter(
                                    filter_expression,
                                    ScanExtendedViewRow.profile_id,
                                    filters.descriptor["keys"][filter_key],
                                )
                            else:
                                if filter_key == "target_id":
                                    flt = get_sqlachemy_filter(
                                        filter_expression,
                                        ScanExtendedViewRow.target_id,
                                        filters.descriptor["keys"][filter_key],
                                    )
                                else:
                                    if filter_key == "scan_id":
                                        flt = get_sqlachemy_filter(
                                            filter_expression,
                                            ScanExtendedViewRow.scan_id,
                                            filters.descriptor["keys"][filter_key],
                                        )
            if flt is not None:
                tail_filters.append(flt)

        query = sql_select(
            (ScanExtendedViewRow, ProfileRow.name.label("profile_name"))
        ).select_from(select_from)
        for flt in tail_filters:
            query = query.where(flt)

        query = (
            query.limit(limit)
            .offset(cursor)
            .order_by(
                sql_nulls_last(
                    sql_desc(
                        sql_func.greatest(
                            ScanExtendedViewRow.next_run,
                            ScanExtendedViewRow.current_start_date,
                        )
                    )
                ),
                ScanExtendedViewRow.scan_id,
            )
        )
        result = ClsScanListResponse(
            scans=[], pagination=ClsPagination(previous_cursor=cursor, next_cursor=None)
        )
        with Connection(self.current_shard_name) as (db_connection):
            for scan in db_connection.execute(query).fetchall():
                scan = dict(scan)
                self._Scans__re_process_scan_result_dict(scan)
                result.scans.append(ClsScanItemResponse(**scan))

        scan_count = len(result.scans)
        if scan_count >= limit:
            result.pagination.next_cursor = cursor + scan_count
        else:
            result.pagination.next_cursor = None
        return result

    def pause_scan(self, scan_id, **_):
        if not settings.get("features.pause_resume"):
            raise Http404()
        require_at_least_tester(self.request_user)
        select_from, tail_filters = generate_scan_ext_query(self.request_user)
        select_from = select_from.join(
            ProfilesTable, ProfileRow.profile_id == ScanExtendedViewRow.profile_id
        )
        query = sql_select(
            (
                ScanExtendedViewRow.current_session_id,
                ScanExtendedViewRow.current_status,
                ProfileRow.jobs,
            )
        ).select_from(select_from)
        tail_filters.append(sql_not(ScanExtendedViewRow.continuous.is_(True)))
        query = apply_filters(query, tail_filters)
        query = query.where(ScanExtendedViewRow.scan_id == scan_id)
        with Connection(self.current_shard_name) as (db):
            scan = db.execute(query).fetchone()
            if not scan:
                raise Http404()
            if scan.current_session_id is None:
                raise Http409(
                    "Scan is not running", ApiErrors.PAUSE_SCAN_IS_NOT_RUNNING
                )
            if scan.current_status != "processing":
                raise Http409(
                    "Invalid scan status", ApiErrors.PAUSE_INVALID_SCAN_STATUS
                )
            query = (
                ScanSessionJobsTable.update(
                    values=dict(
                        pause_requested=dict(
                            date=datetime.datetime.now(tzutc()),
                            reason=ScanPauseReasonTypes.USER,
                        )
                    )
                )
            ).where(ScanSessionJobRow.scan_session_id == scan.current_session_id)
            db.execute(query)
            query = (ScanSessionsTable.update(values=dict(status="pausing"))).where(
                ScanSessionRow.scan_session_id == scan.current_session_id
            )
            db.execute(query)
            event_query = create_event(
                "scan_user_paused",
                get_owner_id(self.request_user),
                self.request_user["user_id"],
                resource_type=EventResources.scan,
                resource_id=scan_id,
                shard=self.current_shard_name,
            )
            db.execute(event_query)
        return Http204()

    def resume_scan(self, scan_id, **_):
        if not settings.get("features.pause_resume"):
            raise Http404()
        require_at_least_tester(self.request_user)
        select_from, tail_filters = generate_scan_ext_query(self.request_user)
        query = sql_select(
            (ScanExtendedViewRow.current_session_id, ScanExtendedViewRow.current_status)
        ).select_from(select_from)
        tail_filters.append(sql_not(ScanExtendedViewRow.continuous.is_(True)))
        query = apply_filters(query, tail_filters)
        query = query.where(ScanExtendedViewRow.scan_id == scan_id)
        with Connection(self.current_shard_name) as (db):
            scan = db.execute(query).fetchone()
            if not scan:
                raise Http404()
            if scan.current_session_id is None:
                raise Http409("Scan is not paused", ApiErrors.PAUSE_SCAN_IS_NOT_RUNNING)
            if scan.current_status != "paused":
                raise Http409(
                    "Invalid scan status", ApiErrors.PAUSE_INVALID_SCAN_STATUS
                )
            q = sql_select(
                (ScanSessionJobRow.abort_requested, ScanSessionJobRow.status)
            ).where(ScanSessionJobRow.scan_session_id == scan.current_session_id)
            for job in db.execute(q).fetchall():
                if job.status != "paused":
                    raise Http409(
                        "Invalid scan status", ApiErrors.PAUSE_INVALID_SCAN_STATUS
                    )

            query = (
                ScanSessionJobsTable.update(
                    values=dict(
                        status="resuming",
                        result_processed=False,
                        pause_requested=False,
                        start_deadline=datetime.datetime.now(tzlocal())
                        + (datetime.timedelta(minutes=15)),
                    )
                )
            ).where(ScanSessionJobRow.scan_session_id == scan.current_session_id)
            db.execute(query)
            query = (ScanSessionsTable.update(values=dict(status="resuming"))).where(
                ScanSessionRow.scan_session_id == scan.current_session_id
            )
            db.execute(query)
            event_query = create_event(
                "scan_user_resumed",
                get_owner_id(self.request_user),
                self.request_user["user_id"],
                resource_type=EventResources.scan,
                resource_id=scan_id,
                shard=self.current_shard_name,
            )
            db.execute(event_query)
        return Http204()

    def abort_scan(self, scan_id, **_):
        """
        Requires: requires at least tester
        :param scan_id:
        :param _:
        :return:
        """
        require_at_least_tester(self.request_user)
        select_from, tail_filters = generate_scan_ext_query(self.request_user)
        query = sql_select(
            (ScanExtendedViewRow.current_session_id, ScanExtendedViewRow.current_status)
        ).select_from(select_from)
        tail_filters.append(sql_not(ScanExtendedViewRow.continuous.is_(True)))
        query = apply_filters(query, tail_filters)
        query = query.where(ScanExtendedViewRow.scan_id == scan_id)
        with Connection(self.current_shard_name) as (db_connection):
            scan = db_connection.execute(query).fetchone()
            if not scan:
                return Http404()
            if scan.current_session_id is None:
                return Http409("Nothing to abort")
            if scan.current_status in ("done", "failed", "aborted", "aborting"):
                return Http409("Invalid scan status")
            if scan.current_status in ("queued", "starting", "processing"):
                query = (
                    ScanSessionJobsTable.update(values=dict(abort_requested=True))
                ).where(ScanSessionJobRow.scan_session_id == scan.current_session_id)
                db_connection.execute(query)
                query = (
                    ScanSessionsTable.update(values=dict(status="aborting"))
                ).where(ScanSessionRow.scan_session_id == scan.current_session_id)
                db_connection.execute(query)
                event_query = create_event(
                    "scan_user_aborted",
                    get_owner_id(self.request_user),
                    self.request_user["user_id"],
                    resource_type=EventResources.scan,
                    resource_id=scan_id,
                    shard=self.current_shard_name,
                )
                db_connection.execute(event_query)
        return Http204()

    def remove_scan(self, scan_id, **_):
        """
        Requires: tech admin
        :param scan_id:
        :param _:
        :return:
        """
        require_at_least_tech_admin(self.request_user)
        select_from, tail_filters = generate_scan_query(self.request_user)
        query = sql_select((ScanRow.scan_id, ScanRow.current_session_id)).select_from(
            select_from
        )
        tail_filters.append(sql_not(ScanExtendedViewRow.continuous.is_(True)))
        query = apply_filters(query, tail_filters)
        query = query.where(ScanRow.scan_id == scan_id)
        with Connection(self.current_shard_name) as (db_connection):
            scan = db_connection.execute(query).fetchone()
            if not scan:
                return Http404()
            if scan.current_session_id:
                q = (
                    ScansTable.update(
                        values=dict(deleted_at=datetime.datetime.now(tzutc()))
                    )
                ).where(ScanRow.scan_id == scan_id)
                db_connection.execute(q)
            else:
                q = ScansTable.delete().where(ScanRow.scan_id == scan_id)
                db_connection.execute(q)
            event_query = create_event(
                "scan_deleted",
                get_owner_id(self.request_user),
                self.request_user["user_id"],
                resource_type=EventResources.scan,
                resource_id=scan_id,
                shard=self.current_shard_name,
            )
            db_connection.execute(event_query)
            return Http204()

    @classmethod
    def __re_process_scan_result_dict(cls, scan):
        scan["target"] = dict(
            address=scan["address"],
            criticality=scan["criticality"],
            description=scan["description"],
        )
        if scan["current_session_id"]:
            severity_counts = dict(
                info=scan["current_vuln_info"],
                low=scan["current_vuln_low"],
                medium=scan["current_vuln_medium"],
                high=scan["current_vuln_high"],
            )
        else:
            severity_counts = None
        scan["current_session"] = dict(
            progress=scan["current_progress"],
            threat=scan["current_threat"],
            scan_session_id=scan["current_session_id"],
            start_date=scan["current_start_date"],
            severity_counts=severity_counts,
            status=scan["current_status"],
            event_level=scan.get("event_level", 0),
        )
        scan["schedule"] = dict(
            disable=scan["schedule_disabled"],
            history_limit=scan["history_limit"],
            start_date=scan["schedule_start_date"],
            recurrence=scan["recurrence"],
            time_sensitive=scan["schedule_time_sensitive"],
        )

    def get_scan(self, scan_id, **_):
        """
        require: anyone with access to the target
        :param scan_id:
        :param _:
        :return:
        """
        select_from, tail_filters = generate_scan_ext_query(self.request_user)
        select_from = select_from.join(
            ProfilesTable, ProfileRow.profile_id == ScanExtendedViewRow.profile_id
        )
        query = sql_select(
            (ScanExtendedViewRow, ProfileRow.name.label("profile_name"))
        ).select_from(select_from)
        tail_filters.append(sql_not(ScanExtendedViewRow.continuous.is_(True)))
        query = apply_filters(query, tail_filters)
        query = query.where(ScanExtendedViewRow.scan_id == scan_id)
        with Connection(self.current_shard_name) as (db_connection):
            scan = db_connection.execute(query).fetchone()
            if not scan:
                return Http404()
            scan = dict(scan)
            self._Scans__re_process_scan_result_dict(scan)
            return ClsScanItemResponse(**scan)

    def update_scan(self, scan_id, body, **_):
        """
        requires at least tester with access to the target
        :param scan_id:
        :param body:
        :param _:
        :return:
        """
        require_at_least_tester(self.request_user)
        self.license.require_active_license()
        new_scan_data = ClsScan(
            ignored_required_keys=True, ignore_default_values=True, **body
        )
        select_from, tail_filters = generate_scan_ext_query(self.request_user)
        tail_filters.append(ScanExtendedViewRow.deleted_at.is_(None))
        tail_filters.append(ScanExtendedViewRow.scan_id == scan_id)
        tail_filters.append(sql_not(ScanExtendedViewRow.continuous.is_(True)))
        select_from = select_from.join(
            TargetsTable, TargetRow.target_id == ScanExtendedViewRow.target_id
        )
        query_scan = apply_filters(
            sql_select(
                (
                    ScanExtendedViewRow,
                    TargetRow.type.label("target_type"),
                    TargetRow.verification,
                )
            ).select_from(select_from),
            tail_filters,
        )
        with Connection(self.current_shard_name) as (db_connection):
            scan = db_connection.execute(query_scan).fetchone()
            if not scan:
                return Http404()
            update_data = {}
            if hasattr(new_scan_data, "report_template_id"):
                if scan.report != new_scan_data.report_template_id:
                    update_data["report_template_id"] = new_scan_data.report_template_id
            profile_id = new_scan_data.get("profile_id")
            if profile_id:
                if profile_id != scan.profile_id:
                    if is_setup_type_aws():
                        if self.request_user["license_data"]["product_code"] in (
                            "OVSTRIAL",
                            "OVS_FREE_MODE",
                        ):
                            raise Http409("trial user can't change scanning profile")
                    query_profile = (
                        sql_select((ProfileRow.jobs,))
                        .where(ProfileRow.profile_id == scan.profile_id)
                        .where(ProfileRow.deleted_at.is_(None))
                        .where(
                            sql_or(
                                ProfileRow.owner_id == get_owner_id(self.request_user),
                                ProfileRow.owner_id.is_(None),
                            )
                        )
                    )
                    profile = db_connection.execute(query_profile).fetchone()
                    if not profile:
                        return Http409("profile not found")
                    update_data["profile_id"] = new_scan_data.profile_id
            schedule = new_scan_data.get("schedule")
            if schedule:
                schedule_time_sensitive = new_scan_data.schedule.get("time_sensitive")
                if schedule_time_sensitive is not None:
                    if scan.schedule_time_sensitive != schedule_time_sensitive:
                        update_data["schedule_time_sensitive"] = schedule_time_sensitive
                start_date = new_scan_data.schedule.get("start_date", "----")
                if start_date != "----":
                    if start_date != scan.schedule_start_date:
                        if scan.recurrence is None:
                            if scan.current_session_id:
                                raise BadApiRequest("Can't change scan start date")
                            update_data["schedule_start_date"] = start_date
                            if start_date is None:
                                update_data["next_run"] = sql_func.now()
                            else:
                                update_data["next_run"] = start_date
                            disable = new_scan_data.schedule.get("disable")
                            if disable is not None:
                                if disable != scan.schedule_disabled:
                                    update_data["schedule_disabled"] = disable
                                    if scan.recurrence:
                                        update_data["schedule_disabled"] = disable
                                        if disable is False:
                                            rrule_set = dateutil.rrule.rruleset()
                                            rrule_set.rrule(
                                                dateutil.rrule.rrulestr(scan.recurrence)
                                            )
                                            next_run = rrule_set.after(
                                                datetime.datetime.now(tzutc())
                                                - (datetime.timedelta(days=1)),
                                                True,
                                            )
                                            if next_run is None:
                                                raise BadApiRequest("Invalid rrule")
                                            update_data["next_run"] = next_run
                                        else:
                                            if scan.current_session_id:
                                                raise BadApiRequest(
                                                    "Can't enable/disable scan schedule"
                                                )
                                            update_data["schedule_disabled"] = disable
                                        new_recurrence = new_scan_data.schedule.get(
                                            "recurrence"
                                        )
                                        if new_recurrence is not None:
                                            if new_recurrence != scan.recurrence:
                                                if scan.recurrence:
                                                    update_data[
                                                        "recurrence"
                                                    ] = new_recurrence
                                                    if scan.next_run:
                                                        rrule_set = (
                                                            dateutil.rrule.rruleset()
                                                        )
                                                        rrule_set.rrule(
                                                            dateutil.rrule.rrulestr(
                                                                new_recurrence
                                                            )
                                                        )
                                                        next_run = rrule_set.after(
                                                            datetime.datetime.now(
                                                                tzutc()
                                                            )
                                                            - (
                                                                datetime.timedelta(
                                                                    days=1
                                                                )
                                                            ),
                                                            True,
                                                        )
                                                        if next_run is None:
                                                            raise BadApiRequest(
                                                                "Invalid rrule"
                                                            )
                                                        update_data[
                                                            "next_run"
                                                        ] = next_run
                                                    else:
                                                        update_data[
                                                            "recurrence"
                                                        ] = new_recurrence
                                                        if scan.next_run:
                                                            rrule_set = (
                                                                dateutil.rrule.rruleset()
                                                            )
                                                            rrule_set.rrule(
                                                                dateutil.rrule.rrulestr(
                                                                    new_recurrence
                                                                )
                                                            )
                                                            next_run = rrule_set.after(
                                                                datetime.datetime.now(
                                                                    tzutc()
                                                                )
                                                                - (
                                                                    datetime.timedelta(
                                                                        days=1
                                                                    )
                                                                ),
                                                                True,
                                                            )
                                                            if next_run is None:
                                                                raise BadApiRequest(
                                                                    "Invalid rrule"
                                                                )
                                                            update_data[
                                                                "next_run"
                                                            ] = next_run
                                                    if update_data:
                                                        result = db_connection.execute(
                                                            (
                                                                ScansTable.update(
                                                                    values=update_data
                                                                )
                                                            ).where(
                                                                ScanRow.scan_id
                                                                == scan_id
                                                            )
                                                        )
                                                        if result.rowcount:
                                                            event_query = create_event(
                                                                "scan_modified",
                                                                get_owner_id(
                                                                    self.request_user
                                                                ),
                                                                self.request_user[
                                                                    "user_id"
                                                                ],
                                                                data=update_data,
                                                                resource_type=EventResources.scan,
                                                                resource_id=scan_id,
                                                                shard=self.current_shard_name,
                                                            )
                                                            db_connection.execute(
                                                                event_query
                                                            )
                                                    return Http204()

    def __validate_aws_scan_params(self, db, user, target, profile):
        scanning_apps = list(profile.jobs.keys())
        if user["license_data"]["product_code"] == "OVS_FREE_MODE":
            if ScanAppTypes.WVS in scanning_apps:
                raise Http409("invalid license")
            if target.type == TargetTypes.DEMO:
                raise Http409("invalid license")
            if self.request_user.get("confirmation") not in (
                UserConfirmation.ADMIN_VALIDATED,
                UserConfirmation.AUTO_VALIDATED,
            ):
                raise Http409("user not validated")
        else:
            if user["license_data"]["product_code"] == "OVSTRIAL":
                sf = ScanSessionsTable.join(
                    ProfilesTable,
                    ProfileRow.profile_id == ScanSessionRow.used_profile_id,
                )
                sf = sf.join(
                    TargetsTable, TargetRow.target_id == ScanSessionRow.target_id
                )
                q = (
                    sql_select(
                        (
                            ScanSessionRow.status,
                            ProfileRow.jobs,
                            TargetRow.type.label("target_type"),
                        )
                    )
                    .select_from(sf)
                    .where(
                        ScanSessionRow.scan_id.in_(
                            sql_select((ScanRow.scan_id,)).where(
                                ScanRow.owner_id == get_owner_id(user)
                            )
                        )
                    )
                )
                web_scan_count = 0
                web_failed_scan_count = 0
                demo_web_scan_count = 0
                demo_network_scan_count = 0
                for r in db.execute(q).fetchall():
                    if ScanAppTypes.WVS in r.jobs:
                        if r.status == "failed":
                            web_failed_scan_count += 1
                        else:
                            web_scan_count += 1
                        if r.target_type == TargetTypes.DEMO:
                            if ScanAppTypes.WVS in r.jobs:
                                demo_web_scan_count += 1
                            if ScanAppTypes.OVAS in r.jobs:
                                pass
                        demo_network_scan_count += 1

                if ScanAppTypes.WVS in scanning_apps:
                    if web_scan_count >= 5:
                        raise Http409("max web scans count reached")
                    if web_failed_scan_count >= 25:
                        raise Http409("max web scans count reached")
                if ScanAppTypes.OVAS in scanning_apps:
                    if target.type != TargetTypes.DEMO:
                        if self.request_user.get("confirmation") not in (
                            UserConfirmation.ADMIN_VALIDATED,
                            UserConfirmation.AUTO_VALIDATED,
                        ):
                            raise Http409("user not validated")
                        else:
                            if demo_network_scan_count >= 10:
                                raise Http409("max network demo scans count reached")
                if target.type == TargetTypes.NETWORK_ONLY:
                    if ScanAppTypes.WVS in scanning_apps:
                        raise Http409("target requires network only profile")
                    else:
                        if ScanAppTypes.WVS in scanning_apps:
                            if self.request_user["extra"].get("trusted_account"):
                                pass
                            else:
                                if target.verification == TargetVerificationTypes.NONE:
                                    raise Http409("target not verified")

    def schedule_scan(self, body, **_):
        """
        requires at least tester with access to the target
        :param body:
        :param _:
        :return:
        """
        require_at_least_tester(self.request_user)
        scan = ClsScan(**body)
        self.license.require_active_license()
        if is_setup_type_aws():
            if scan.get("user_authorized_to_scan") != "yes":
                raise Http409("scan not authorized")
        recurrence = scan.schedule.get("recurrence")
        schedule_disabled = scan.schedule.get("disabled") is True
        history_limit = scan.schedule.get("history_limit") if recurrence else None
        schedule_start_date = None
        if is_setup_type_aws():
            if self.request_user["license_type"] == "trial":
                if recurrence or scan.schedule.get("start_date"):
                    raise Http409("trial user can't schedule scans")
        if recurrence:
            if is_setup_type_aws():
                if self.request_user["license_type"] == "trial":
                    raise Http409("trial account can't schedule recurrent scans")
            rrule_set = dateutil.rrule.rruleset()
            rrule_set.rrule(dateutil.rrule.rrulestr(recurrence))
            next_run = rrule_set.after(
                datetime.datetime.now(tzutc()) - (datetime.timedelta(days=1)), True
            )
            if next_run is None:
                raise BadApiRequest("Invalid rrule")
            else:
                schedule_start_date = scan.schedule.get("start_date", "---")
                if not schedule_start_date or schedule_start_date == "---":
                    schedule_start_date = None
                    next_run = sql_func.now()
                else:
                    if is_setup_type_aws():
                        if self.request_user["license_type"] == "trial":
                            raise Http409(
                                "trial account can't schedule scans in te future"
                            )
                    next_run = schedule_start_date
                scan_id = str(uuid.uuid4())
                scan_data = dict(
                    scan_id=scan_id,
                    owner_id=get_owner_id(self.request_user),
                    creator_id=self.request_user["user_id"],
                    target_id=scan.target_id,
                    profile_id=scan.profile_id,
                    recurrence=recurrence,
                    schedule_disabled=schedule_disabled,
                    history_limit=history_limit,
                    schedule_start_date=schedule_start_date,
                    next_run=next_run,
                    schedule_time_sensitive=scan.schedule.get("time_sensitive"),
                    report_template_id=scan.get("report_template_id"),
                )
                select_from, tail_filters = generate_target_xh_query(self.request_user)
                query_target = apply_filters(
                    sql_select((TargetsWithExcludedHoursViewRow,)).select_from(
                        select_from
                    ),
                    tail_filters,
                )
                query_target = query_target.where(
                    TargetsWithExcludedHoursViewRow.target_id == scan.target_id
                )
                with Connection(self.current_shard_name) as (db_connection):
                    system_excluded_hours = get_system_excluded_hours(db_connection)
                    target = db_connection.execute(query_target).fetchone()
                    if not target:
                        return Http409("target not found")
                    query_profile = (
                        sql_select((ProfileRow.jobs,))
                        .where(ProfileRow.profile_id == scan.profile_id)
                        .where(ProfileRow.deleted_at.is_(None))
                        .where(
                            sql_or(
                                ProfileRow.owner_id == get_owner_id(self.request_user),
                                ProfileRow.owner_id.is_(None),
                            )
                        )
                    )
                    profile = db_connection.execute(query_profile).fetchone()
                    if not profile:
                        return Http409("profile not found")
                    if is_setup_type_aws():
                        self._Scans__validate_aws_scan_params(
                            db_connection, self.request_user, target, profile
                        )
                    target_xh_data = target.xh_data
                    target_xh_time_offset = target.xh_time_offset
                    ui_session_id = None
                    if has_feature(SetupFeatures.MANUAL_BROWSING):
                        if target.manual_intervention:
                            ui_session_id = scan.get("ui_session_id")
                            if ui_session_id is None:
                                raise Http409(
                                    "target requires manual intervention",
                                    code=ApiErrors.MI_NO_SESSION_ID,
                                )
                            if self.remote_address not in get_local_addresses():
                                raise Http409(
                                    "target requires manual intervention",
                                    code=ApiErrors.MI_NOT_FROM_LOCALHOST,
                                )
                            if recurrence:
                                raise Http409(
                                    "target requires manual intervention",
                                    code=ApiErrors.MI_RECURRENT_SCANS_NOT_ALLOWED,
                                )
                            if schedule_start_date or schedule_disabled:
                                raise Http409(
                                    "target requires manual intervention",
                                    code=ApiErrors.MI_ONLY_SCAN_NOW_ALLOWED,
                                )
                    if target_xh_data:
                        if not is_allowed_to_scan_now(
                            target_xh_data, target_xh_time_offset
                        ):
                            raise Http409(
                                "target requires manual intervention",
                                code=ApiErrors.MI_SCAN_NOW_NOT_POSSIBLE_TARGET_EXCLUDED_HOURS,
                            )
                        else:
                            if system_excluded_hours:
                                if not (
                                    is_allowed_to_scan_now(**system_excluded_hours)
                                ):
                                    raise Http409(
                                        "target requires manual intervention",
                                        code=ApiErrors.MI_SCAN_NOW_NOT_POSSIBLE_SYSTEM_EXCLUDED_HOURS,
                                    )
                        query_scan = ScansTable.insert().values(**scan_data)
                        try:
                            db_connection.execute(query_scan)
                        except psycopg2.IntegrityError as e:
                            if "(profile_id)" in str(e):
                                raise BadApiRequest("Invalid profile_id")

                        if ui_session_id:
                            scan_data["ui_session_id"] = ui_session_id
                        event_query = create_event(
                            "scan_scheduled",
                            get_owner_id(self.request_user),
                            self.request_user["user_id"],
                            data=scan_data,
                            resource_type=EventResources.scan,
                            resource_id=self.request_user["user_id"],
                            shard=self.current_shard_name,
                        )
                        db_connection.execute(event_query)
            if has_feature(SetupFeatures.MANUAL_BROWSING):
                if target.manual_intervention:
                    settings["manual_browsing_storage"].register_ui_session_id(
                        scan_id, ui_session_id
                    )
            headers = None
        if schedule_start_date is None:
            if target_xh_data:
                if not is_allowed_to_scan_now(target_xh_data, target_xh_time_offset):
                    headers = {
                        "X-Schedule-Delayed": str(
                            get_next_date_allowed_to_scan(
                                target_xh_data, target_xh_time_offset
                            )
                        )
                    }
                else:
                    if system_excluded_hours:
                        if not (is_allowed_to_scan_now(**system_excluded_hours)):
                            headers = {
                                "X-Schedule-Delayed": str(
                                    get_next_date_allowed_to_scan(
                                        **system_excluded_hours
                                    )
                                )
                            }
                scan.ui_session_id = ui_session_id
                return Http201(
                    location=self.base_path + "/scans/" + scan_data["scan_id"],
                    body=scan,
                    headers=headers,
                )

    def get_scan_result_history(self, scan_id, l=None, c=None, **_):
        """
        Accessible by everyone with access to the targets
        :param scan_id:
        :param l:
        :param c:
        :param _:
        :return:
        """
        limit = min(l, 100) if l is not None else 100
        c = int(c) if c is not None else 0
        cursor = c
        select_from, tail_filters = generate_scan_session_query(self.request_user)
        query = sql_select(
            (
                ScanSessionRow.scan_session_id,
                ScanSessionRow.start_expected,
                ScanSessionRow.start_date,
                ScanSessionRow.end_date,
                ScanSessionRow.status,
            )
        ).select_from(select_from)
        query = apply_filters(query, tail_filters)
        query = query.where(sql_not(ScanRow.continuous.is_(True)))
        query = query.where(ScanSessionRow.scan_id == scan_id)
        scan_session_list = []
        query = (
            query.limit(limit)
            .offset(cursor)
            .order_by(sql_desc(ScanSessionRow.start_date), ScanSessionRow.scan_id)
        )
        with Connection(self.current_shard_name) as (db_connection):
            for scan_session in db_connection.execute(query).fetchall():
                scan_session_list.append(
                    ClsScanResultItem(
                        result_id=scan_session.scan_session_id,
                        start_date=scan_session.start_date
                        if scan_session.start_date
                        else scan_session.start_expected,
                        end_date=scan_session.end_date,
                        status=scan_session.status,
                        scan_id=scan_id,
                    )
                )

        item_count = len(scan_session_list)
        if item_count >= limit:
            next_cursor = cursor + item_count
        else:
            next_cursor = None
        return ClsScanResultListResponse(
            pagination=ClsPagination(previous_cursor=cursor, next_cursor=next_cursor),
            results=scan_session_list,
        )
